﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System.Globalization;
using Microsoft.ApplicationInsights.Channel;

namespace Microsoft.DotNet.Cli.Telemetry.PersistenceChannel;

internal class StorageTransmission : Transmission, IDisposable
{
    internal Action<StorageTransmission> Disposing;

    protected StorageTransmission(string fullPath, Uri address, byte[] content, string contentType,
        string contentEncoding)
        : base(address, content, contentType, contentEncoding)
    {
        FullFilePath = fullPath;
        FileName = Path.GetFileName(fullPath);
    }

    internal string FileName { get; }

    internal string FullFilePath { get; }

    /// <summary>
    ///     Disposing the storage transmission.
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    ///     Creates a new transmission from the specified <paramref name="stream" />.
    /// </summary>
    /// <returns>Return transmission loaded from file; return null if the file is corrupted.</returns>
    internal static async Task<StorageTransmission> CreateFromStreamAsync(Stream stream, string fileName)
    {
        StreamReader reader = new(stream);
        Uri address = await ReadAddressAsync(reader).ConfigureAwait(false);
        string contentType = await ReadHeaderAsync(reader, "Content-Type").ConfigureAwait(false);
        string contentEncoding = await ReadHeaderAsync(reader, "Content-Encoding").ConfigureAwait(false);
        byte[] content = await ReadContentAsync(reader).ConfigureAwait(false);
        return new StorageTransmission(fileName, address, content, contentType, contentEncoding);
    }

    /// <summary>
    ///     Saves the transmission to the specified <paramref name="stream" />.
    /// </summary>
    internal static async Task SaveAsync(Transmission transmission, Stream stream)
    {
        StreamWriter writer = new(stream);
        try
        {
            await writer.WriteLineAsync(transmission.EndpointAddress.ToString()).ConfigureAwait(false);
            await writer.WriteLineAsync("Content-Type" + ":" + transmission.ContentType).ConfigureAwait(false);
            await writer.WriteLineAsync("Content-Encoding" + ":" + transmission.ContentEncoding)
                .ConfigureAwait(false);
            await writer.WriteLineAsync(string.Empty).ConfigureAwait(false);
            await writer.WriteAsync(Convert.ToBase64String(transmission.Content)).ConfigureAwait(false);
        }
        finally
        {
            writer.Flush();
        }
    }

    private static async Task<string> ReadHeaderAsync(TextReader reader, string headerName)
    {
        string line = await reader.ReadLineAsync().ConfigureAwait(false);
        if (string.IsNullOrEmpty(line))
        {
            throw new FormatException(string.Format(CultureInfo.InvariantCulture, "{0} header is expected.",
                headerName));
        }

        string[] parts = line.Split(':');
        if (parts.Length != 2)
        {
            throw new FormatException(string.Format(CultureInfo.InvariantCulture,
                "Unexpected header format. {0} header is expected. Actual header: {1}", headerName, line));
        }

        if (parts[0] != headerName)
        {
            throw new FormatException(string.Format(CultureInfo.InvariantCulture,
                "{0} header is expected. Actual header: {1}", headerName, line));
        }

        return parts[1].Trim();
    }

    private static async Task<Uri> ReadAddressAsync(TextReader reader)
    {
        string addressLine = await reader.ReadLineAsync().ConfigureAwait(false);
        if (string.IsNullOrEmpty(addressLine))
        {
            throw new FormatException("Transmission address is expected.");
        }

        Uri address = new(addressLine);
        return address;
    }

    private static async Task<byte[]> ReadContentAsync(TextReader reader)
    {
        string content = await reader.ReadToEndAsync().ConfigureAwait(false);
        if (string.IsNullOrEmpty(content) || content == Environment.NewLine)
        {
            throw new FormatException("Content is expected.");
        }

        return Convert.FromBase64String(content);
    }

    private void Dispose(bool disposing)
    {
        if (disposing)
        {
            Action<StorageTransmission> disposingDelegate = Disposing;
            disposingDelegate?.Invoke(this);
        }
    }
}
